package appcore

import (
	"fmt"
	cliflag "gitee.com/vrv_media/go-micro-framework/pkg/common/cli/flag"
	"gitee.com/vrv_media/go-micro-framework/pkg/common/cli/globalflag"
	"gitee.com/vrv_media/go-micro-framework/pkg/common/term"
	"gitee.com/vrv_media/go-micro-framework/pkg/common/util/fileutil"
	"gitee.com/vrv_media/go-micro-framework/pkg/common/version"
	"gitee.com/vrv_media/go-micro-framework/pkg/common/version/verflag"
	"gitee.com/vrv_media/go-micro-framework/pkg/config"
	"gitee.com/vrv_media/go-micro-framework/pkg/errors"
	"gitee.com/vrv_media/go-micro-framework/pkg/logger"
	"gitee.com/vrv_media/go-micro-framework/pkg/options"
	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"os"
	"runtime"
	"strings"
)

var (
	progressMessage = color.GreenString("==>")
)

// CoreApp 是一个命令行应用程序的主要结构。
// 建议使用 app.NewCoreApp() 函数创建一个 app
type CoreApp struct {
	basename    string
	name        string
	description string
	// 应用程序的配置
	appOptions CliConfigOptions
	// 主程序的配置，主要用于解析应用程序的配置的方式
	mainConfigOptions *config.ConfigOptions
	runFunc           RunFunc
	// 表示当命令出现错误时，不会打印使用情况
	silence   bool
	noVersion bool
	noConfig  bool
	commands  []*Command
	args      cobra.PositionalArgs
	cmd       *cobra.Command
}

// NewCoreApp 根据给定的应用程序名称、项目文件名称以及其他选项创建一个新的应用程序实例
func NewCoreApp(name string, basename string, opts ...Option) *CoreApp {
	a := &CoreApp{
		name:              name,     // 启动 app 名字
		basename:          basename, // 启动 项目 名字
		mainConfigOptions: config.NewConfigOptions(),
	}
	for _, opt := range opts { // 执行附加参数
		opt(a)
	}
	a.buildCommand()
	return a
}

// Run 用于启动应用程序
func (a *CoreApp) Run() {
	if err := a.cmd.Execute(); err != nil {
		fmt.Printf("%v %v\n", color.RedString("Error:"), err)
		os.Exit(1)
	}
}

// CliConfigOptions abstracts configuration options for reading parameters from the command line.
// 配置文件实现的的添加和验证flag命令行的接口
type CliConfigOptions interface {
	Flags() (fss cliflag.NamedFlagSets) // 生成一个被 cliflag.NamedFlagSets 封装后的日志对象
	Validate() []error                  // 添加你想要的验证函数
}

// RunFunc 定义应用程序的启动回调函数
type RunFunc func(basename string) error

// Option 定义了初始化应用程序结构的可选参数
type Option func(*CoreApp)

// WithOptions 用于开启应用程序从命令行读取参数或从配置文件中读取参数的功能
func WithOptions(opt CliConfigOptions) Option {
	return func(a *CoreApp) {
		a.appOptions = opt
	}
}

// WithRunFunc 用于设置应用程序启动回调函数的选项
func WithRunFunc(run RunFunc) Option {
	return func(a *CoreApp) {
		a.runFunc = run
	}
}

// WithDescription 用于设置应用程序的描述
func WithDescription(desc string) Option {
	return func(a *CoreApp) {
		a.description = desc
	}
}

func WithMainConfigNative(fileName string, fileType string) Option {
	return func(a *CoreApp) {
		a.mainConfigOptions = &config.ConfigOptions{
			Type:       "native",
			NativePath: fileutil.GetCurrentDirectory(),
			NativeName: fileName,
			NativeType: fileType,
		}
	}
}

// WithSilence 用于将应用程序设置为静默模式，在该模式下，程序启动信息、配置信息和版本信息不会在控制台中打印出来
func WithSilence() Option {
	return func(a *CoreApp) {
		a.silence = true
	}
}

// WithNoVersion 设置应用程序不提供版本标志
func WithNoVersion() Option {
	return func(a *CoreApp) {
		a.noVersion = true
	}
}

// WithNoConfig 设置应用程序 不使用 config 文件
func WithNoConfig() Option {
	return func(a *CoreApp) {
		a.noConfig = true
	}
}

// WithValidArgs set the validation function to valid non-flag arguments.
func WithValidArgs(args cobra.PositionalArgs) Option {
	return func(a *CoreApp) {
		a.args = args
	}
}

// WithDefaultValidArgs set default validation function to valid non-flag arguments.
func WithDefaultValidArgs() Option {
	return func(a *CoreApp) {
		a.args = func(cmd *cobra.Command, args []string) error {
			for _, arg := range args {
				if len(arg) > 0 {
					return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
				}
			}
			return nil
		}
	}
}

func (a *CoreApp) buildCommand() {
	cmd := cobra.Command{
		Use:   FormatBaseName(a.basename),
		Short: a.name,
		Long:  a.description,
		// stop printing usage when the command errors
		SilenceUsage:  true, // 表示当命令出现错误时，不会打印使用情况
		SilenceErrors: true, // 表示当命令出现错误时，不会输出任何内容
		Args:          a.args,
	}
	// cmd.SetUsageTemplate(usageTemplate)
	cmd.SetOut(os.Stdout)          // 输入到标准输出流(cobra 默认 不填也没事 )
	cmd.SetErr(os.Stderr)          // 输出到 标准错误输出流(cobra 默认 不填也没事)
	cmd.Flags().SortFlags = true   // 参数配置排序 按照字母顺序显示在帮助文档中
	cliflag.InitFlags(cmd.Flags()) // 规范化标志的名称和值 并且让 Cobra 库支持 Go 标准库 flag 包的所有功能

	// 设置 cobra 子命令
	if len(a.commands) > 0 {
		for _, command := range a.commands {
			cmd.AddCommand(command.cobraCommand())
		}
		// 设置帮助命令
		cmd.SetHelpCommand(helpCommand(a.name))
	}
	// 设置 corbra.RunE  解析成功后 运行此方法
	if a.runFunc != nil {
		cmd.RunE = a.runCommand
	}
	fs := cmd.Flags()
	// 首先添加配置文件的提示信息
	var namedFlagSets cliflag.NamedFlagSets
	if a.appOptions != nil {
		namedFlagSets = a.appOptions.Flags()
	}
	if !a.noConfig {
		a.mainConfigOptions.AddFlags(namedFlagSets.FlagSet("config"))
	}
	if len(namedFlagSets.FlagSets) > 0 {
		for _, f := range namedFlagSets.FlagSets { // 遍历 map[string]*pflag.FlagSet
			fs.AddFlagSet(f) // 集成到 cobra 中  AddFlagSet将一个 FlagSet 添加到另一个 FlagSet
		}
		usageFmt := "Usage:\n  %s\n"
		cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
		cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
			fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine()) // 输出到指定输出流中
			cliflag.PrintSections(cmd.OutOrStdout(), namedFlagSets, cols)              // cols：一个整数，表示终端的列数，用于控制输出的宽度。
		})
		cmd.SetUsageFunc(func(cmd *cobra.Command) error {
			fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine())
			cliflag.PrintSections(cmd.OutOrStderr(), namedFlagSets, cols) // cols：一个整数，表示终端的列数，用于控制输出的宽度。

			return nil
		})
	}

	if !a.noVersion {
		// 添加版本号标志 到 global flagSet 下
		verflag.AddFlags(namedFlagSets.FlagSet("global"))
	}

	if !a.noConfig {
		// 首先从env中获取参数
		config.InitConfigFromEnv(a.mainConfigOptions)
	}
	globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name())
	a.cmd = &cmd
}

// cobra 最后执行的函数
func (a *CoreApp) runCommand(cmd *cobra.Command, args []string) error {
	// 打印工作的目录
	printWorkingDir()
	// 打印所有的 Flags
	cliflag.PrintFlags(cmd.Flags())
	if !a.noVersion {
		// display application version information
		verflag.PrintAndExitIfRequested()
	}
	// 如果不是不需要读取配置文件的设置，首先读取配置文件
	if !a.noConfig {

		err := viper.BindPFlags(cmd.Flags())
		if err != nil {
			return err
		}
		// 适配一下cmd的主配置
		if err := viper.Unmarshal(a.mainConfigOptions); err != nil {
			return err
		}
		// 先从命令行读取
		if err := viper.Unmarshal(a.appOptions); err != nil {
			return err
		}
		// 从配置文件中获取
		config.AnalyzeConfig(a.mainConfigOptions, a.appOptions)
	}

	if !a.silence {
		logger.InfoF("%v Starting %s ...", progressMessage, a.name)
		if !a.noVersion {
			logger.InfoF("%v Version: `%s`", progressMessage, version.Get().ToJSON())
		}
		if !a.noConfig {
			logger.InfoF("%v Config file used: `%s`", progressMessage, viper.ConfigFileUsed())
		}
	}
	if a.appOptions != nil {
		if err := a.applyOptionRules(); err != nil {
			return err
		}
	}
	// run application
	if a.runFunc != nil {
		return a.runFunc(a.basename)
	}
	return nil
}

func (a *CoreApp) applyOptionRules() error {
	// 这里可以添加 自定义规则 只需要 添加 Complete方法
	if completeableOptions, ok := a.appOptions.(options.CompletableOptions); ok {
		if err := completeableOptions.Complete(); err != nil {
			return err
		}
	}

	// 验证 字段是否合规
	if errs := a.appOptions.Validate(); len(errs) != 0 {
		return errors.NewAggregate(errs)
	}

	// 这里可以天机 String 方法 就会打印输出以下的内容
	if printableOptions, ok := a.appOptions.(options.PrintableOptions); ok && !a.silence {
		logger.InfoF("%v Config: `%s`", progressMessage, printableOptions.String())
	}

	return nil
}

func printWorkingDir() {
	wd, _ := os.Getwd()
	logger.InfoF("%v WorkingDir: %s", progressMessage, wd)
}

// Command 方法返回应用程序中的 cobra.Command 实例
func (a *CoreApp) Command() *cobra.Command {
	return a.cmd
}

// AddCommand 向应用程序添加多个子命令
func (a *CoreApp) AddCommand(cmds ...*Command) {
	a.commands = append(a.commands, cmds...)
}

// FormatBaseName 根据给定的名称，根据不同操作系统格式化为可执行文件名
func FormatBaseName(basename string) string {
	// Make case-insensitive and strip executable suffix if present
	if runtime.GOOS == "windows" {
		basename = strings.ToLower(basename)
		basename = strings.TrimSuffix(basename, ".exe")
	}

	return basename
}
